﻿using CocoaAni.Net.WebSockets.Enums;

namespace CocoaAni.Net.WebSockets.Frames;

public class FrameHeader
{
    public FrameHeader()
    {
        HeaderBuffer = Array.Empty<byte>();
    }

    public FrameHeader(int size)
    {
        if (size == 0)
        {
            HeaderBuffer = Array.Empty<byte>();
            return;
        }
        HeaderBuffer = new byte[size];
    }

    public FrameHeader(byte[] headerBuffer)
    {
        HeaderBuffer = headerBuffer;
    }

    public FrameHeader(byte[] headerBuffer, int headerStartIndex)
    {
        HeaderBuffer = headerBuffer;
        HeaderStartIndex = headerStartIndex;
    }

    public FrameHeader(ReceiveBuffer msgBuffer)
    {
        HeaderBuffer = msgBuffer.Buffer;
        HeaderStartIndex = msgBuffer.HandleCursor;
        if (msgBuffer.IsSaveHeader) return;
        HeaderBuffer = msgBuffer.Buffer[HeaderStartIndex..(HeaderStartIndex + HeaderLength)];
        HeaderStartIndex = 0;
    }

    public void CopyHeaderFromBuffer()
    {
        HeaderBuffer = HeaderBuffer[HeaderStartIndex..(HeaderStartIndex + HeaderLength)];
    }

    public void ResetHeader()
    {
        var headerLen = HeaderLength;
        for (var i = HeaderStartIndex; i < HeaderStartIndex + headerLen; i++)
        {
            HeaderBuffer[i] = 0;
        }
    }

    public void SetHeader(bool isFin, bool rsv1, bool rsv2, bool rsv3, WSOpcode opcode, ulong payloadLength, byte[]? mask = null)
    {
        HeaderBuffer[HeaderStartIndex] |= (byte)(isFin ? 0x80 : 0x00);
        HeaderBuffer[HeaderStartIndex] |= (byte)(rsv1 ? 0x80 : 0x00);
        HeaderBuffer[HeaderStartIndex] |= (byte)(rsv2 ? 0x20 : 0x00);
        HeaderBuffer[HeaderStartIndex] |= (byte)(rsv3 ? 0x10 : 0x00);
        HeaderBuffer[HeaderStartIndex] &= 0xF0;
        HeaderBuffer[HeaderStartIndex] |= (byte)opcode;
        HeaderBuffer[HeaderStartIndex + 1] &= 0x80;
        switch (payloadLength)
        {
            case <= 125:
                HeaderBuffer[HeaderStartIndex + 1] |= (byte)payloadLength;
                break;

            case <= 0xFFFF:
                HeaderBuffer[HeaderStartIndex + 1] |= 126;
                HeaderBuffer[HeaderStartIndex + 2] |= (byte)(payloadLength >> 8 & 0xFF);
                HeaderBuffer[HeaderStartIndex + 3] |= (byte)(payloadLength & 0xFF);
                break;

            default:
                HeaderBuffer[HeaderStartIndex + 1] |= 127;
                var moveId = 7;
                for (var i = 2; i < 10; i++)
                {
                    HeaderBuffer[HeaderStartIndex + i] = (byte)(payloadLength >> moveId * 8 & 0xFF);
                    moveId--;
                }
                break;
        }

        if (mask == null)
        {
            HasMask = false;
            return;
        }
        HasMask = true;
        var maskI = 0;
        for (var i = HeaderLength - 4; i < HeaderLength; i++)
        {
            HeaderBuffer[i] = mask[maskI++];
        }
    }

    public void SetHeader(bool isFin, WSOpcode opcode, ulong payloadLength, byte[]? mask = null)
    {
        HeaderBuffer[HeaderStartIndex] |= (byte)(isFin ? 0x80 : 0x00);
        HeaderBuffer[HeaderStartIndex] &= 0xF0;
        HeaderBuffer[HeaderStartIndex] |= (byte)opcode;
        HeaderBuffer[HeaderStartIndex + 1] &= 0x80;
        switch (payloadLength)
        {
            case <= 125:
                HeaderBuffer[HeaderStartIndex + 1] |= (byte)payloadLength;
                break;

            case <= 0xFFFF:
                HeaderBuffer[HeaderStartIndex + 1] |= 126;
                HeaderBuffer[HeaderStartIndex + 2] |= (byte)(payloadLength >> 8 & 0xFF);
                HeaderBuffer[HeaderStartIndex + 3] |= (byte)(payloadLength & 0xFF);
                break;

            default:
                HeaderBuffer[HeaderStartIndex + 1] |= 127;
                var moveId = 7;
                for (var i = 2; i < 10; i++)
                {
                    HeaderBuffer[HeaderStartIndex + i] = (byte)(payloadLength >> moveId * 8 & 0xFF);
                    moveId--;
                }
                break;
        }

        if (mask == null)
        {
            HasMask = false;
            return;
        }
        HasMask = true;
        var maskI = 0;
        for (var i = HeaderLength - 4; i < HeaderLength; i++)
        {
            HeaderBuffer[i] = mask[maskI++];
        }
    }

    public void SetHeader(bool isFin, WSOpcode opcode, int payloadLength, byte[]? mask = null)
    {
        HeaderBuffer[HeaderStartIndex] |= (byte)(isFin ? 0x80 : 0x00);
        HeaderBuffer[HeaderStartIndex] &= 0xF0;
        HeaderBuffer[HeaderStartIndex] |= (byte)opcode;
        HeaderBuffer[HeaderStartIndex + 1] &= 0x80;
        switch (payloadLength)
        {
            case <= 125:
                HeaderBuffer[HeaderStartIndex + 1] |= (byte)payloadLength;
                break;

            case <= 0xFFFF:
                HeaderBuffer[HeaderStartIndex + 1] |= 126;
                HeaderBuffer[HeaderStartIndex + 2] |= (byte)(payloadLength >> 8 & 0xFF);
                HeaderBuffer[HeaderStartIndex + 3] |= (byte)(payloadLength & 0xFF);
                break;

            default:
                HeaderBuffer[HeaderStartIndex + 1] |= 127;
                var moveId = 7;
                for (var i = 2; i < 10; i++)
                {
                    HeaderBuffer[HeaderStartIndex + i] = (byte)(payloadLength >> moveId * 8 & 0xFF);
                    moveId--;
                }
                break;
        }

        if (mask == null)
        {
            HasMask = false;
            return;
        }
        HasMask = true;
        var maskI = 0;
        for (var i = HeaderLength - 4; i < HeaderLength; i++)
        {
            HeaderBuffer[i] = mask[maskI++];
        }
    }

    public byte[] HeaderBuffer { get; set; }
    public Memory<byte> Header=> HeaderBuffer.AsMemory(HeaderStartIndex,HeaderLength);
    public int HeaderStartIndex { get; set; }

    public bool IsFinal
    {
        get => (HeaderBuffer[HeaderStartIndex] & 0x80) == 0x80;
        set => HeaderBuffer[HeaderStartIndex] |= (byte)(value ? 0x80 : 0x00);
    }

    public bool Rsv1
    {
        get => (HeaderBuffer[HeaderStartIndex] & 0x40) == 0x40;
        set => HeaderBuffer[HeaderStartIndex] |= (byte)(value ? 0x40 : 0x00);
    }

    public bool Rsv2
    {
        get => (HeaderBuffer[HeaderStartIndex] & 0x20) == 0x20;
        set => HeaderBuffer[HeaderStartIndex] |= (byte)(value ? 0x20 : 0x00);
    }

    public bool Rsv3
    {
        get => (HeaderBuffer[HeaderStartIndex] & 0x10) == 0x10;
        set => HeaderBuffer[HeaderStartIndex] |= (byte)(value ? 0x10 : 0x00);
    }

    public WSOpcode Opcode
    {
        get => (WSOpcode)(HeaderBuffer[HeaderStartIndex] & 0x0F);
        set
        {
            HeaderBuffer[HeaderStartIndex] &= 0xF0;
            HeaderBuffer[HeaderStartIndex] |= (byte)value;
        }
    }

    public bool HasMask
    {
        get => (HeaderBuffer[HeaderStartIndex + 1] & 0x80) == 0x80;
        set => HeaderBuffer[HeaderStartIndex + 1] |= (byte)(value ? 0x80 : 0x00);
    }

    public byte[]? Masks
    {
        get => !HasMask ? null : HeaderBuffer[(HeaderLength - 4)..HeaderLength];
        set
        {
            if (value == null)
            {
                HasMask = false;
                return;
            }
            HasMask = true;
            var maskI = 0;
            for (var i = HeaderLength - 4; i < HeaderLength; i++)
            {
                HeaderBuffer[i] = value[maskI++];
            }
        }
    }

    public int HeaderLength
    {
        get
        {
            var len = HeaderBuffer[HeaderStartIndex + 1] & 0x7F;
            return (HasMask ? 4 : 0) +
                   (len==0?0:len < 125 ? 2 : len == 126 ? 4 : 10);
        }
    }

    public int PayloadLength
    {
        get
        {
            var lenOrMode = HeaderBuffer[HeaderStartIndex + 1] & 0x7F;
            switch (lenOrMode)
            {
                case 126:
                    return (ushort)(HeaderBuffer[HeaderStartIndex + 2] << 8 | HeaderBuffer[HeaderStartIndex + 3]);

                case 127:
                    ulong len = 0;
                    for (var i = 2; i < 10; i++)
                    {
                        len <<= 8;
                        len |= HeaderBuffer[HeaderStartIndex + i];
                    }
                    return (int)len;

                default: return lenOrMode;
            }
        }
        set
        {
            HeaderBuffer[HeaderStartIndex + 1] &= 0x80;
            switch (value)
            {
                case <= 125:
                    HeaderBuffer[HeaderStartIndex + 1] |= (byte)value;
                    break;

                case <= 0xFFFF:
                    HeaderBuffer[HeaderStartIndex + 1] |= 126;
                    HeaderBuffer[HeaderStartIndex + 2] |= (byte)(value >> 8 & 0xFF);
                    HeaderBuffer[HeaderStartIndex + 3] |= (byte)(value & 0xFF);
                    break;

                default:
                    HeaderBuffer[HeaderStartIndex + 1] |= 127;
                    var moveId = 3;
                    for (var i = 6; i < 10; i++)
                    {
                        HeaderBuffer[HeaderStartIndex + i] = (byte)(value >> moveId * 8 & 0xFF);
                        moveId--;
                    }
                    break;
            }
        }
    }
}